package com.wyk.session;

import com.wyk.annotations.Param;
import com.wyk.config.Function;
import com.wyk.config.mapperBean;
import com.wyk.handler.ParameterMappingTokenHandler;
import com.wyk.parsing.GenericTokenParser;

import java.beans.Introspector;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName: wykMapperProxy
 * @Description: todo 执行代理对象方法的类
 * @Author: 初久
 * @Date: 2022/7/13 14:13
 **/
public class wykMapperProxy implements InvocationHandler {

    private wykConfiguration myConfiguration;

    private wykSqlsession mySqlsession;

    public wykMapperProxy(wykConfiguration myConfiguration, wykSqlsession mySqlsession) {
        this.myConfiguration = myConfiguration;
        this.mySqlsession = mySqlsession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {

        Class<?> mapperClass = method.getDeclaringClass();

        // todo 读取该mapper接口对应的xml文件
        mapperBean readMapper = myConfiguration.readMapper("mapper/" + mapperClass.getSimpleName() + ".xml");

        //是否是xml文件对应的接口
        if (!mapperClass.getName().equals(readMapper.getInterfaceName())) {
            throw new NullPointerException("mapper对象的接口方法在xml文件中未匹配");
        }

        // todo 读取这个xml文件中的所有sql statement标签
        List<Function> list = readMapper.getFunctionList();
        if (null != list && 0 != list.size()) {
            for (Function function : list) {
                String methodName = method.getName();
                //id是否和接口方法名一样
                if (methodName.equals(function.getFuncName())) {

                    /*
                     * todo 参数解析、创建Map映射
                     * 参数名 : 值
                     * "name" : "zs"
                     * "arg0" : "zs"
                     **/
                    Parameter[] parameters = method.getParameters();
                    Map<String, Object> paramValuesMap = new HashMap<>();

                    try {
                        drudgeType(parameters, paramValuesMap, args);
                    } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | InstantiationException e) {
                        throw new RuntimeException("参数解析失败");
                    }


                    // todo 解析sql ps:这个是从mybatis源码里面拿的,只需要知道这三步把#{xxx}变成了?并且存储了xxx的集合就行
                    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler();
                    GenericTokenParser sqlParser = new GenericTokenParser("#{", "}", handler);
                    String sql = sqlParser.parse(function.getSql());


                    // todo 所以在这里就规定所有的查询方法前缀必须为select
                    if (Introspector.decapitalize(methodName).startsWith("select")) {
                        // 查询语句
                        return mySqlsession.query(sql, handler, paramValuesMap, method);
                    } //增、删、改语句
                    return mySqlsession.update(sql, handler, paramValuesMap, method);
                }
            }
        }
        return null;
    }

    public void drudgeType(Parameter[] parameters, Map<String, Object> paramValuesMap, Object[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {

        for (int i = 0; i < parameters.length; i++) {

            Parameter parameter = parameters[i];
            Type parameterizedType = parameter.getParameterizedType();
            // todo 判断是否为基本数据类型的包装类型
            if (parameterizedType == Integer.class || parameterizedType == String.class || parameterizedType == Byte.class || parameterizedType == Short.class || parameterizedType == Boolean.class || parameterizedType == Double.class || parameterizedType == Float.class || parameterizedType == Long.class) {
                // todo 直接赋值

                // todo 支持原生的arg0,arg1...写法
                paramValuesMap.put(parameter.getName(), args[i]);
                // todo 支持@Param注解上设置名字的写法
                paramValuesMap.put(parameter.getAnnotation(Param.class).value(), args[i]);
            } else {
                // todo 这里规定除了上述之外都是实体类

                // todo 如果这是实体类，则拿出并存储它里面的属性的名字和对应的值
                Class<?> aClass = Class.forName(parameterizedType.getTypeName());

                for (Field field : aClass.getDeclaredFields()) {
                    for (Field declaredField : args[i].getClass().getDeclaredFields()) {
                        if (field.getName().equals(declaredField.getName())) {
                            declaredField.setAccessible(true);
                            paramValuesMap.put(field.getName(), declaredField.get(args[i]));
                        }
                    }
                }

            }
        }

    }

}